#include "guiterm.hpp"
#include <unistd.h>

FileTransfer::FileTransfer(ServerConnection *source, string filename)
{
	this->host = source;
	this->uploading = false;
	this->paused = true;
	this->cancelling = false;
	
	this->listReceiver = NULL;
	this->request = NULL;
	this->currentUpload = NULL;
	
	files.push_back(filename);
	this->activeFile = 0;
	this->localDir = "";
	this->forceFilename = true;
}

FileTransfer::FileTransfer(bool upload, ServerConnection *source, const RPCParams *params)
{
	this->host = source;
	this->uploading = upload;
	this->paused = true;
	this->cancelling = false;
	
	this->listReceiver = new FileListReceiver(source, params);
	connect(listReceiver, SIGNAL(setFileList(const vector<string>&)), this, SLOT(setFileList(const vector<string>&)));
	this->request = NULL;
	this->currentUpload = NULL;
	
	this->activeFile = 0;
	this->localDir = "";
	this->forceFilename = false;
}

FileTransfer::~FileTransfer()
{
	delete request;
	delete listReceiver;
}

bool FileTransfer::isUpload()
{
	return uploading;
}

bool FileTransfer::isPaused()
{
	return paused;
}

void FileTransfer::begin(string dir)
{
	this->localDir = dir;
	paused = false;
	
	emit transferBegun();
	
	activeFile = 0;
	startFile(files[activeFile]);
}

void FileTransfer::pause()
{
	if(paused) return;
	if(cancelling) return;
	paused = true;
	
	if(uploading) {
		// TODO
	} else {
		// TODO
	}
}

void FileTransfer::resume()
{
	if(!paused) return;
	if(cancelling) return;
	paused = false;
	
	if(uploading) {
		// TODO
	} else {
		// TODO
	}
}

void FileTransfer::cancel()
{
	cancelling = true;
	
	if(uploading) {
		if(currentUpload)
			currentUpload->cancel();
		emit transferFinished(false, "Cancelled");
	} else {
		if(request)
			request->cancel();
		emit transferFinished(false, "Cancelled");
	}
}

void FileTransfer::setFileList(const vector<string> &files)
{
	this->files = files;
	emit listReady(this->files);
}

void FileTransfer::onFileFinished()
{
	finishFile();
	
	if(cancelling) {
		emit transferFinished(false, "Cancelled");
		return;
	}
	
	activeFile++;
	if(activeFile >= (int)files.size())
		finish();
	else
		startFile(files[activeFile]);
}

void FileTransfer::onFileError(string message)
{
	emit fileFailed(files[activeFile], message);
	onFileFinished();
}

void FileTransfer::onUpdateProgress(size_t downloaded, size_t total)
{
	double progress = (double)downloaded / (double)total;
	emit updateProgress(files[activeFile], progress);
}

void FileTransfer::finishFile()
{
	if(request) {
		request->deleteLater();
		request = NULL;
	}
	if(currentUpload) {
		currentUpload->deleteLater();
		currentUpload = NULL;
	}
	
	emit fileFinished(files[activeFile], localFilename);
}

void FileTransfer::startFile(string filename)
{
	if(uploading)
	{
		Path path(filename.c_str());
		string basename = path.basename();
		Path localPath = Path(basename.c_str()).resolve(Path(localDir.c_str()));
		localFilename = localPath.toString();
		
		emit fileBegun(filename);
		FILE *file = fopen(localPath.toString().c_str(), "rb");
		
		int id = host->newTransferId();
		RPCParams putMessageParams;
			putMessageParams.set("path", filename);
			putMessageParams.set("id", id);
			currentUpload = new FileUploader(file, id, host, &putMessageParams);
		host->call("put", &putMessageParams);
		
		connect(currentUpload, SIGNAL(finished(const char*)), this, SLOT(onFileFinished()));
		connect(currentUpload, SIGNAL(updateProgress(size_t,size_t)), this, SLOT(onUpdateProgress(size_t,size_t)));
		
		host->attachUploader(currentUpload);
	}
	else
	{
		if(forceFilename) {
			localFilename = localDir;
		} else {
			string basename = Path(filename).basename();
			localFilename = Path(basename).resolve(Path(localDir)).toString();
		}
		string partialFilename = localFilename + ".filepart";
		
		request = new FileRequest(this, host, filename, localFilename, partialFilename);
		connect(request, SIGNAL(finished()), this, SLOT(onFileFinished()));
		connect(request, SIGNAL(failed(string)), this, SLOT(onFileError(string)));
		emit fileBegun(filename);
	}
}

void FileTransfer::finish()
{
	emit transferFinished(true, "Done");
}

//////////////////////////////////////////////////////////////////////////////

FileListReceiver::FileListReceiver(RemoteHost *source, const RPCParams *params)
	:StreamReceiver(source, params)
{
}

void FileListReceiver::recv(int len, const char *data)
{
	buf.append(len, data);
}

void FileListReceiver::onClose()
{
	buf.appendByte(0);
	vector<string> files;
	separateLines(files, buf.getData());
	for(unsigned ii=0; ii<files.size(); ii++)
		files[ii] = unquoteString(files[ii].c_str());
	
	setFileList(files);
}

/////////////////////////////////////////////////////////////////////////////

FileRequest::FileRequest(FileTransfer *transfer, ServerConnection *host, string remoteFilename, string localFilename, string partialFilename)
{
	this->transfer = transfer;
	this->host = host;
	this->localFilename = localFilename;
	this->partialFilename = partialFilename;
	this->receiver = NULL;
	
	this->id = host->newTransferId();
	
	RPCParams getParams;
		getParams.set("id", id);
		getParams.set("path", remoteFilename);
	host->call("get", &getParams);
	
	host->attachDownloader(id, this);
}

FileRequest::~FileRequest()
{
	host->removeDownloader(id);
	
	if(receiver->isClosed())
		delete receiver;
}

void FileRequest::begin(RemoteHost *host, const RPCParams *params)
{
	FILE *file = fopen(partialFilename.c_str(), "wb");
	receiver = new FileReceiver(file, host, params);
	connect(receiver, SIGNAL(updateProgress(size_t,size_t)), transfer, SLOT(onUpdateProgress(size_t,size_t)));
	connect(receiver, SIGNAL(finished(const char*)), this, SLOT(complete(const char*)));
}

void FileRequest::cancel()
{
	receiver->cancel();
	unlink(partialFilename.c_str());
}

void FileRequest::complete(const char *errorMessage)
{
	if(errorMessage) {
		emit failed(errorMessage);
	} else {
		if(unlink(localFilename.c_str()) != 0) {
			if(errno != ENOENT) {
				emit failed(retprintf("Failed removing old file: %s", strerror(errno)));
				return;
			}
		}
		
		if(rename(partialFilename.c_str(), localFilename.c_str()) < 0) {
			emit failed(retprintf("Failed renaming %s: %s.", partialFilename.c_str(), strerror(errno)));
		} else {
			emit finished();
		}
	}
}

/////////////////////////////////////////////////////////////////////////////

FileReceiver::FileReceiver(FILE *file, RemoteHost *source, const RPCParams *params)
	:StreamReceiver(source, params)
{
	this->file = file;
	this->received = 0;
	this->size = params->getInt64("size");
	updateProgress(0, size);
}

FileReceiver::~FileReceiver()
{
	if(file)
		fclose(file);
}

void FileReceiver::recv(int len, const char *data)
{
	if(!file) return;
	
	fwrite(data, 1, len, file);
	received += len;
	updateProgress(received, size);
}

void FileReceiver::cancel()
{
	if(file) {
		fclose(file);
		file = NULL;
		finished("Cancelled");
	}
}

void FileReceiver::onClose()
{
	if(file) {
		fclose(file);
		file = NULL;
		finished(NULL);
	}
}

void FileReceiver::disconnected()
{
	fclose(file);
	file = NULL;
	finished("Disconnected");
}

/////////////////////////////////////////////////////////////////////////////

FileUploader::FileUploader(FILE *file, int id, ServerConnection *host, RPCParams *params)
	:StreamSender(host, params)
{
	this->host = host;
	this->id = id;
	this->completed = 0;
	this->file = file;
	
	// Seek to the end of the file to find its file size, then go back to the
	// beginning.
	if(fseek(file, 0, SEEK_END) != 0) {
		// Seek failed -> not a regular file
		// Can try uploading anyways, but can't have a meaningful progress bar
		this->size = 1;
	} else {
		this->size = (size_t)ftell(file);
		fseek(file, 0, SEEK_SET);
	}
}

FileUploader::~FileUploader()
{
	if(file)
		fclose(file);
	host->removeUploader(this);
}

void FileUploader::doSend()
{
	if(!file) return;
	
	if(feof(file)) {
		fclose(file);
		file = NULL;
		finished(NULL);
		
		close();
		
		RPCParams putFinishedParams;
			putFinishedParams.set("id", id);
		host->call("putfinished", &putFinishedParams);
		
		trace << "Finished upload at EOF\n"; //DEBUG
		return;
	}
	
	char buf[1024];
	int bytesRead = fread(buf, 1, 1024, file);
	
	if(bytesRead > 0) {
		send(bytesRead, buf);
		completed += bytesRead;
		updateProgress(completed, size);
	} else {
		fclose(file);
		file = NULL;
		
		trace << "Finished upload with an error\n"; //DEBUG
		finished(strerror(errno));
	}
}

void FileUploader::cancel()
{
	close();
	
	RPCParams putCancelParams;
		putCancelParams.set("id", id);
	host->call("putcancel", &putCancelParams);
	
	fclose(file);
	file = NULL;
	finished("Cancelled");
}

void FileUploader::pause()
{
	// TODO
}

void FileUploader::unpause()
{
	// TODO
}

/////////////////////////////////////////////////////////////////////////////

RPC_HANDLER(getresp)
{
	FileRequest *request = getRequest(host, params);
	if(request)
		request->begin(host, params);
}

RPC_HANDLER(getfailed)
{
	FileRequest *request = getRequest(host, params);
	string message = params->getString("message");
	request->complete(message.c_str());
}

RPC_HANDLER(confirmget)
{
	RemoteJob *job = getJob(host, params);
	JobView *jobView = job->getView();
	
	FileTransfer *transfer = new FileTransfer(false, (ServerConnection*)host, params);
	FileTransferView *view = new FileTransferView(job, transfer);
	jobView->addSegment(view);
}

RPC_HANDLER(requestput)
{
	RemoteJob *job = getJob(host, params);
	JobView *jobView = job->getView();
	
	FileTransfer *transfer = new FileTransfer(true, (ServerConnection*)host, params);
	FileTransferView *view = new FileTransferView(job, transfer);
	jobView->addSegment(view);
}

RPC_HANDLER(putfailed)
{
	// TODO
	trace << "File upload failed\n"; //DEBUG
}

